"""
Relations between lists.

DISCUSSION:
    Sometimes related data is stored in separate lists, without any explicit
    statement to that effect. This is the case with OpenFlashChart2 linear 
    data, where the X-values are stored in a "labels" list, while the related
    Y-values are stored in another list called "values". Sometimes, these are
    stored in very different parts of a nested JSON semantic tree (as with OFC).
    
    These classes provide a way to relate lists. They work by gathering all
    the list items during the "merge" stage, then relating the items just
    prior to the "aggregation" stage. Because this requires some state-keeping,
    these are not as simple as the plain merge methods and aggregation methods.
    
    Also, it may not be obvious how this all works, so please read the tests
    and work through them as examples, before using these relations.
"""
import logging

from agg_methods import first

import log_setup

class Correlation(object):
    """
    Correlates two lists, making one list's values index values from the second list.
    """
    def __init__(self, name, value_agg_method=first, sort_by_index=True):
        self.name = name
        self.value_agg_method = value_agg_method
        self.sort_by_index = sort_by_index
        self._index_data = []
        self._value_data = []
        self._already_indexed = False
        self._indexed_values = [] # (index, value list) tuples
        
    def _index_values(self):
        """
        Actually does the correlation between values and their indices.
        """
        # We don't know whether agg_index() or agg_value() will be called first.
        # So, do this only once.
        if self._already_indexed:
            return
            
        # Correlate all values for their appropriate indices.
        tmp = {} # index: value list
        unsorted_indexes = []
        for index, value in zip(self._index_data, self._value_data):
            if index not in unsorted_indexes:
                unsorted_indexes.append(index)
            tmp.setdefault(index, [])
            tmp[index].append(value)
        logging.debug('Unsorted indexes: %s' % unsorted_indexes)
            
        # Now, aggregate the values per index.
        self._indexed_values = []
        method = self.value_agg_method
        for index in unsorted_indexes:
            values = tmp[index]
            final_value = method(values)
            self._indexed_values.append( (index, final_value) )
        
        logging.debug('Indexed values: %s' % self._indexed_values)
        
        if self.sort_by_index:
            self._indexed_values.sort()
            logging.debug('Sorted values: %s' % self._indexed_values)
        self._already_indexed = True
        
    def _check_merge_ok(self):
        if self._already_indexed:
            raise Exception('Cannot merge after indexing.')
        
    def agg_index(self, series):
        self._index_values()
        return [ index for (index, value) in self._indexed_values ]
    
    def agg_value(self, series):
        self._index_values()
        return [ value for (index, value) in self._indexed_values ]
    
    def merge_index(self, list1, list2, merge_methods={}, parent_keys=() ):
        self._check_merge_ok()
        if list1:
            self._index_data.extend(list1)
        if list2:
            self._index_data.extend(list2)
        return []
    
    def merge_value(self, list1, list2, merge_methods={}, parent_keys=() ):
        self._check_merge_ok()    
        if list1:
            self._value_data.extend(list1)
        if list2:
            self._value_data.extend(list2)
        return []
    
    
